//*****************************************************************************
// file        : ad2428.c
// ad2428 device driver.
// Copyright (c) 2024-2024 Dynaudio Technology co. Ltd. All rights reserved
//
// Change Logs:
// Date                 Author      Note
// 2024/03/01           Cheney      First draft version
// 2024/03/05           Cheney      1. Add peripheral access api.
//                                  2. Add remote a2b slave note pin access api.
// 2024/04/26           Cheney      Add ad2428 setup async api.
//
//*****************************************************************************

//*****************************************************************************
//
//! \addtogroup ad2428
//! @{
//
//*****************************************************************************
#include "ad2428_pal.h"
#include "ad2428.h"
#include "string.h"

#define WRITE   ((unsigned char) 0x00u)
#define READ    ((unsigned char) 0x01u)
#define DELAY   ((unsigned char) 0x02u)
#define INVALID ((unsigned char) 0xffu)

/**
 * @brief ad2428 setup async state
 *
 */
typedef enum
{
    AD2428_SETUP_ASYNC_STATE_IDLE,
    AD2428_SETUP_ASYNC_STATE_DELAY,
    AD2428_SETUP_ASYNC_STATE_EXEC_CMD,
} Ad2428SetupAsyncState_t;

typedef struct
{
    A2bNetworkInfo_t *info;
    Ad2428ConfigList_t *config;
    uint32_t config_length;
    uint32_t config_index;
    Ad2428SetupAsyncState_t state;
    uint32_t delay_ms;
    uint8_t op_code;
} Ad2428SetupAsync_t;

static Ad2428SetupAsync_t ad2428SetupAsync;

/**
 * @brief buffer to store data
 *
 */
static uint8_t aDataBuffer[6000];
static uint8_t aDataWriteReadBuf[4u];

/**
 * @brief ad2428 init
 *
 * @return init operation result.
 */
int ad2428_init(void)
{
    return ad2428_i2c_init();
}

/**
 * @brief ad2428 deinit
 *
 * @return deinit operation result.
 */
int ad2428_deinit(void)
{
    return ad2428_i2c_deinit();
}

/**
 * @brief read local device register.
 *
 * @param nDeviceAddress device address.
 * @param nRegAddr register address.
 * @param regVal read register value.
 * @return operation result.
 */
int ad2428_read_local_reg(A2bNetworkInfo_t *info, uint8_t nRegAddr, uint8_t *regVal)
{
    int status = 0;
    uint8_t wbuf[1];

    wbuf[0] = nRegAddr;

    status = info->I2cWriteReadPtr(info->DeviceAddr, 1, wbuf, 1, regVal);
    return status;
}

/**
 * @brief write local device register
 *
 * @param nDeviceAddress device address.
 * @param nRegAddr register address.
 * @param regVal register value.
 * @return operation result.
 */
int ad2428_write_local_reg(A2bNetworkInfo_t *info, uint8_t nRegAddr, uint8_t regVal)
{
    int status = 0;
    uint8_t wbuf[2];

    wbuf[0] = nRegAddr;
    wbuf[1] = regVal;

    status = info->I2cWritePtr(info->DeviceAddr, 2, wbuf);
    return status;
}

/**
 * @brief read remote device register
 *
 * @param nDeviceAddress device address.
 * @param nNodeAddr remote device node address.
 * @param nRegAddr register address
 * @param nRead length in bytes of the data to receive
 * @param rbuf register value read from remote node.
 * @return operation result.
 */
int ad2428_read_remote_reg(A2bNetworkInfo_t *info, uint8_t nNodeAddr, uint8_t nRegAddr, uint8_t nRead, uint8_t *rbuf)
{
    int status = 0;
    uint8_t wbuf[1];

    /* set node address */
    info->DeviceAddr &= 0xfe;
    status = ad2428_write_local_reg(info, 0x01, nNodeAddr);
    if (status != 0)
    {
        return status;
    }

    /* read register value from other node */
    wbuf[0] = nRegAddr;
    info->DeviceAddr |= 0x01;
    status = info->I2cWriteReadPtr(info->DeviceAddr, 1, wbuf, nRead, rbuf);
    return status;
}

/**
 * @brief write remote device register
 *
 * @param nDeviceAddress device address.
 * @param nNodeAddr remote device node address.
 * @param nRegAddr register address
 * @param nWrite length in bytes of the data to be transferred
 * @param wbuf pointer to the data to be transferred
 * @return Error or success status returned by API
 */
int ad2428_write_remote_reg(A2bNetworkInfo_t *info, uint8_t nNodeAddr, uint8_t nRegAddr, uint8_t nWrite, uint8_t *wbuf)
{
    int status = 0;
    uint8_t buf[10];

    /* set node address */
    info->DeviceAddr &= 0xfe;
    status = ad2428_write_local_reg(info, 0x01, nNodeAddr);
    if (status != 0)
    {
        return status;
    }

    /* write register value to other node */
    buf[0] = nRegAddr;
    memcpy(&buf[1], wbuf, nWrite);
    info->DeviceAddr |= 0x01;
    status = info->I2cWritePtr(info->DeviceAddr, 1 + nWrite, wbuf);
    return status;
}

/**
 * @brief read register values from the peripheral connected with the slave node.
 *
 * @param nDeviceAddress device address.
 * @param nNodeAddr remote device node address.
 * @param ChipAddr peripheral device address.
 * @param nRegAddr peripheral register address
 * @param nRead length in bytes of the data to receive
 * @param rbuf register value read from the peripheral.
 * @return Error or success status returned by API
 */
int ad2428_read_remote_peri_reg(A2bNetworkInfo_t *info, uint8_t nNodeAddr, uint8_t ChipAddr, uint8_t nRegAddr, uint8_t nRead, uint8_t *rbuf)
{
    int status = 0;
    uint8_t wbuf[1];

    /* set chip address in remote node. */
    status = ad2428_write_remote_reg(info, nNodeAddr, 0x00, 1, &ChipAddr);
    if (status != 0)
    {
        return status;
    }

    /* enable Peripheral */
    status = ad2428_write_local_reg(info, 0x01, nNodeAddr | 0x20);
    if (status != 0)
    {
        return status;
    }

    /* read register values from the peripheral connected with the slave node. */
    wbuf[0] = nRegAddr;
    info->DeviceAddr |= 0x01;
    status = info->I2cWriteReadPtr(info->DeviceAddr, 1, wbuf, nRead, rbuf);
    return status;
}

/**
 * @brief write register values to the peripheral connected with the slave node.
 *
 * @param nDeviceAddress device address.
 * @param nNodeAddr remote device node address.
 * @param ChipAddr peripheral device address.
 * @param nRegAddr peripheral register address
 * @param nWrite length in bytes of the data to be transferred
 * @param wbuf pointer to the data to be transferred
 * @return Error or success status returned by API
 */
int ad2428_write_remote_peri_reg(A2bNetworkInfo_t *info, uint8_t nNodeAddr, uint8_t ChipAddr, uint8_t nRegAddr, uint8_t nWrite, uint8_t *wbuf)
{
    int status = 0;
    uint8_t buf[10];

    /* set chip address in remote node. */
    status = ad2428_write_remote_reg(info, nNodeAddr, 0x00, 1, &ChipAddr);
    if (status != 0)
    {
        return status;
    }

    /* enable Peripheral */
    status = ad2428_write_local_reg(info, 0x01, nNodeAddr | 0x20);
    if (status != 0)
    {
        return status;
    }

    /* write register values to the peripheral connected with the slave node. */
    buf[0] = nRegAddr;
    memcpy(&buf[1], wbuf, nWrite);
    info->DeviceAddr |= 0x01;
    status = info->I2cWritePtr(info->DeviceAddr, 1 + nWrite, wbuf);
    return status;
}

/**
 * @brief set remote port direction.
 *
 * @param nDeviceAddress device address.
 * @param nNodeAddr remote device node address.
 * @param dir port direction info. (bit0 - IO0 bit1 - IO1 ... bit7 - IO7. Bit level: 0 - Input 1 - Output.)
 * @return Error or success status returned by API
 */
int ad2428_set_remote_port_direction(A2bNetworkInfo_t *info, uint8_t nNodeAddr, uint8_t dir)
{
    uint8_t oen_reg = 0;
    uint8_t ien_reg = 0;
    int status = -1;

    for (size_t i = 0; i < 8; i++)
    {
        if (dir & (1 << i))
        {
            oen_reg |= (1 << i);
        }
        else
        {
            ien_reg |= (1 << i);
        }
    }

    if(ad2428_write_remote_reg(info, nNodeAddr, 0x4d, 1, &oen_reg) == 0)
    {
        if (ad2428_write_remote_reg(info, nNodeAddr, 0x4e, 1, &ien_reg) == 0)
        {
            status = 0;
        }
    }

    return status;
}

/**
 * @brief write remote io output.
 *
 * @param nDeviceAddress device address.
 * @param nNodeAddr remote device node address.
 * @param val port data value. bit0 - IO0 bit1 - IO1 ... bit7 - IO7
 * @return Error or success status returned by API
 */
int ad2428_write_remote_port(A2bNetworkInfo_t *info, uint8_t nNodeAddr, uint8_t val)
{
    return ad2428_write_remote_reg(info, nNodeAddr, 0x4a, 1, &val);
}

/**
 * @brief rete remote io input state
 *
 * @param nDeviceAddress device address.
 * @param nNodeAddr remote device node address.
 * @param val points to a space to save port data value. bit0 - IO0 bit1 - IO1 ... bit7 - IO7
 * @return Error or success status returned by API
 */
int ad2428_read_remote_port(A2bNetworkInfo_t *info, uint8_t nNodeAddr, uint8_t *val)
{
    return ad2428_read_remote_reg(info, nNodeAddr, 0x4f, 1, val);
}

/**
 * @brief set remote pin output state.
 *
 * @param nDeviceAddress device address.
 * @param nNodeAddr remote device node address.
 * @param pin pin index. (0 ~ 7)
 * @param val pin state. (0 - low level. 1 - high level)
 * @return Error or success status returned by API
 */
int ad2428_set_remote_pin(A2bNetworkInfo_t *info, uint8_t nNodeAddr, uint8_t pin, uint8_t val)
{
    uint8_t reg_val = 1 << pin;

    if (val)
    {
        /* IO data set. */
        return ad2428_write_remote_reg(info, nNodeAddr, 0x4b, 1, &reg_val);
    }
    else
    {
        /* IO data clear. */
        return ad2428_write_remote_reg(info, nNodeAddr, 0x4c, 1, &reg_val);
    }
}

/**
 * @brief get remote pin input state.
 *
 * @param nDeviceAddress device address.
 * @param nNodeAddr remote device node address.
 * @param pin pin index. (0 ~ 7)
 * @param val pin state. (0 - low level. 1 - high level)
 * @return Error or success status returned by API
 */
int ad2428_get_remote_pin(A2bNetworkInfo_t *info, uint8_t nNodeAddr, uint8_t pin, uint8_t *val)
{
    uint8_t port_state;
    int status = -1;

    if (ad2428_read_remote_port(info, nNodeAddr, &port_state) == 0)
    {
        if (port_state & (1 << pin))
        {
            *val = 1;
        }
        else
        {
            *val = 0;
        }

        status = 0;
    }

    return status;
}

/**
 * @brief This function calculates reg value based on width and adds it to the data array
 *
 * @param pDstBuf  Pointer to destination array
 * @param nAddrwidth Data unpacking boundary(1 byte / 2 byte /4 byte )
 * @param nAddr Number of words to be copied
 */
static void adi_a2b_Concat_Addr_Data(uint8_t pDstBuf[], uint32_t nAddrwidth, uint32_t nAddr)
{
    /* Store the read values in the place holder */
    switch (nAddrwidth)
    {
    /* Byte */
    case 1u:
        pDstBuf[0u] = (uint8_t)nAddr;
        break;

    /* 16 bit word*/
    case 2u:
        pDstBuf[0u] = (uint8_t)(nAddr >> 8u);
        pDstBuf[1u] = (uint8_t)(nAddr & 0xFFu);
        break;

    /* 24 bit word */
    case 3u:
        pDstBuf[0u] = (uint8_t)((nAddr & 0xFF0000u) >> 16u);
        pDstBuf[1u] = (uint8_t)((nAddr & 0xFF00u) >> 8u);
        pDstBuf[2u] = (uint8_t)(nAddr & 0xFFu);
        break;

    /* 32 bit word */
    case 4u:
        pDstBuf[0u] = (uint8_t)(nAddr >> 24u);
        pDstBuf[1u] = (uint8_t)((nAddr & 0xFF0000u) >> 16u);
        pDstBuf[2u] = (uint8_t)((nAddr & 0xFF00u) >> 8u);
        pDstBuf[3u] = (uint8_t)(nAddr & 0xFFu);
        break;

    default:
        break;
    }
}

/**
 * @brief setup a2b network
 *
 * @param nDeviceAddress device address.
 * @param config a2b configurations.
 * @param config_length configuration length
 * @return Error or success status returned by API
 */
int ad2428_network_setup_sync(A2bNetworkInfo_t *info, Ad2428ConfigList_t *config, uint32_t config_length)
{
    Ad2428ConfigList_t *pOPUnit;
    uint32_t nIndex, nIndex1;
    int status = 0;

    uint32_t nDelayVal;
    uint32_t bError;

    /* Loop over all the configuration */
    for (nIndex = 0; nIndex < config_length; nIndex++)
    {
        pOPUnit = &config[nIndex];
        /* Operation code*/
        switch (pOPUnit->eOpCode)
        {
        /* Write */
        case WRITE:
            adi_a2b_Concat_Addr_Data(&aDataBuffer[0u], pOPUnit->nAddrWidth, pOPUnit->nAddr);
            (void)memcpy(&aDataBuffer[pOPUnit->nAddrWidth], pOPUnit->paConfigData, pOPUnit->nDataCount);
            /* PAL Call, replace with custom implementation  */
            status = info->I2cWritePtr((int16_t)pOPUnit->nDeviceAddr, (int16_t)(pOPUnit->nAddrWidth + pOPUnit->nDataCount), &aDataBuffer[0u]);
            break;

        /* Read */
        case READ:
            (void)memset(&aDataBuffer[0u], 0u, pOPUnit->nDataCount);
            adi_a2b_Concat_Addr_Data(&aDataBuffer[0u], pOPUnit->nAddrWidth, pOPUnit->nAddr);
            /* PAL Call, replace with custom implementation  */
            status = info->I2cWriteReadPtr((int16_t)pOPUnit->nDeviceAddr, (int16_t)(pOPUnit->nAddrWidth), &aDataWriteReadBuf[0u], (int16_t)pOPUnit->nDataCount, &aDataBuffer[0u]);
            break;

        /* Delay */
        case DELAY:
            nDelayVal = 0u;
            for (nIndex1 = 0u; nIndex1 < pOPUnit->nDataCount; nIndex1++)
            {
                nDelayVal = pOPUnit->paConfigData[nIndex1] | nDelayVal << 8u;
            }
            (void)ad2428_delay(nDelayVal);
            break;

        default:
            break;
        }

        if (status != 0)
        {
            /* I2C/SPI read/write failed!, let us continue */
            bError++;
        }
    }

    return status;
}

/**
 * @brief setup a2b network asynchronously
 *
 * @param nDeviceAddress device address.
 * @param config a2b configurations.
 * @param config_length configuration length
 * @return Error or success status returned by API
 */
int ad2428_network_setup_async(A2bNetworkInfo_t *info, Ad2428ConfigList_t *config, uint32_t config_length)
{
    if (config_length > 1)
    {
        ad2428SetupAsync.info = info;
        ad2428SetupAsync.config = config;
        ad2428SetupAsync.config_length = config_length;
        ad2428SetupAsync.state = AD2428_SETUP_ASYNC_STATE_EXEC_CMD;
        ad2428SetupAsync.config_index = 0;
        ad2428SetupAsync.delay_ms = 0;
    }
    else
    {
        /* invalid length. Report error. */
    }

    return 0;
}

/**
 * @brief ad2428 network setup main function for asynchronous setup
 *
 */
void ad2428_network_setup_mainfunction(void)
{
    bool continue_setup = true;
    uint32_t nDelayVal;
    uint32_t nIndex1;
    Ad2428ConfigList_t *pOPUnit;
    A2bNetworkInfo_t *info = ad2428SetupAsync.info;
    int status = 0;
    uint32_t bError;

    while(continue_setup && (ad2428SetupAsync.state != AD2428_SETUP_ASYNC_STATE_IDLE))
    {
        switch (ad2428SetupAsync.state)
        {
        case AD2428_SETUP_ASYNC_STATE_EXEC_CMD:
            pOPUnit = &ad2428SetupAsync.config[ad2428SetupAsync.config_index++];

            /* Operation code*/
            switch (pOPUnit->eOpCode)
            {
            /* Write */
            case WRITE:
                adi_a2b_Concat_Addr_Data(&aDataBuffer[0u], pOPUnit->nAddrWidth, pOPUnit->nAddr);
                (void)memcpy(&aDataBuffer[pOPUnit->nAddrWidth], pOPUnit->paConfigData, pOPUnit->nDataCount);
                /* PAL Call, replace with custom implementation  */
                status = info->I2cWritePtr((int16_t)pOPUnit->nDeviceAddr, (int16_t)(pOPUnit->nAddrWidth + pOPUnit->nDataCount), &aDataBuffer[0u]);
                break;

            /* Read */
            case READ:
                (void)memset(&aDataBuffer[0u], 0u, pOPUnit->nDataCount);
                adi_a2b_Concat_Addr_Data(&aDataBuffer[0u], pOPUnit->nAddrWidth, pOPUnit->nAddr);
                /* PAL Call, replace with custom implementation  */
                status = info->I2cWriteReadPtr((int16_t)pOPUnit->nDeviceAddr, (int16_t)(pOPUnit->nAddrWidth), &aDataWriteReadBuf[0u], (int16_t)pOPUnit->nDataCount, &aDataBuffer[0u]);
                break;

            /* Delay */
            case DELAY:
                nDelayVal = 0u;
                for (nIndex1 = 0u; nIndex1 < pOPUnit->nDataCount; nIndex1++)
                {
                    nDelayVal = pOPUnit->paConfigData[nIndex1] | nDelayVal << 8u;
                }
                ad2428SetupAsync.delay_ms = nDelayVal;
                ad2428SetupAsync.state = AD2428_SETUP_ASYNC_STATE_DELAY;
                continue_setup = false;
                break;

            default:
                break;
            }

            if (status != 0)
            {
                /* I2C/SPI read/write failed!, let us continue */
                bError++;
            }

            /* if the operation code is not delay, check if all the command list has been processed. */
            if (continue_setup == true)
            {
                if (ad2428SetupAsync.config_index >= ad2428SetupAsync.config_length)
                {
                    ad2428SetupAsync.state = AD2428_SETUP_ASYNC_STATE_IDLE;
                    continue_setup = false;
                }
            }
            break;

        case AD2428_SETUP_ASYNC_STATE_DELAY:
            ad2428SetupAsync.delay_ms = (ad2428SetupAsync.delay_ms > AD2428_MAIN_FUNCTION_PERIOD) ? (ad2428SetupAsync.delay_ms - AD2428_MAIN_FUNCTION_PERIOD) : 0;

            /* if the delay time is reached, check if all the command list has been processed.*/
            if (ad2428SetupAsync.delay_ms == 0)
            {
                if (ad2428SetupAsync.config_index < ad2428SetupAsync.config_length)
                {
                    ad2428SetupAsync.state = AD2428_SETUP_ASYNC_STATE_EXEC_CMD;
                }
                else
                {
                    ad2428SetupAsync.state = AD2428_SETUP_ASYNC_STATE_IDLE;
                    continue_setup = false;
                }
            }
            break;

        default:
            break;
        }
    }
}

//*****************************************************************************
//
// Close the Doxygen group.
//! @}
//
//*****************************************************************************